package com.chaosj.xml;

import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.util.ClassUtil;
import com.alipay.sofa.common.utils.StringUtil;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigChangeListener;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;


/**
 * apollo 配置中心
 * 发布与获取组件
 */
@Slf4j
public class ApolloClientTool<T> {
	private Map<String,Config> configMap = new HashMap<>();
	private Map<String, T> releaseMap = new HashMap<>();

	public ApolloClientTool(ApolloOpen config) {
		System.setProperty("apollo.meta", config.getMeta());
		System.setProperty("app.id", config.getAppId());
		if (null != config.getClusterName() && config.getClusterName().length() > 0) {
			System.setProperty("apollo.cluster", config.getClusterName());
		}
		if (null != config.getEnv() && config.getEnv().length() > 0) {
			System.setProperty("env", config.getEnv());
		}
		// 设置缓存路径
		System.setProperty("apollo.cacheDir", config.getCachePath() + File.separator + "config-cache");
	}

	public <T> void update(String key, String prefix, String value, Map<String,T> data){
		if( key.startsWith(prefix) ){
			List<String> paths = Splitter.on(".").splitToList(key.replaceFirst(prefix.concat("."),""));
			String serviceCode = paths.get(0);
			T instance = data.get(serviceCode);
			BeanPath.create(Joiner.on(".").join(paths.subList(1,paths.size()))).set(instance, value);
		}
	}

	private Config addConfigChangeEvent(String namespace, String prefix){
		Config config = ConfigService.getConfig(namespace);
		config.addChangeListener(new ConfigChangeListener() {
			@Override
			public void onChange(ConfigChangeEvent configChangeEvent) {
				configChangeEvent.changedKeys().forEach(key->{
					update(key,prefix, configMap.get(namespace).getProperty(key,""), releaseMap);
				});
			}
		});
		return config;
	}

	/**
	 * 获取 命名空间中的 key 转换 为 对象
	 * @param namespace
	 * @param prefix
	 * @param clazz
	 * @return
	 */
	public Map<String, T> getReleaseData(String namespace, String prefix, Class<T> clazz) {
		Config config = configMap.computeIfAbsent(namespace, ns->addConfigChangeEvent(ns, prefix));
		List<String> allKeys = new ArrayList<>();
		config.getPropertyNames().stream().filter(key->key.startsWith(prefix.concat(".").concat(""))).forEach(key->{
			allKeys.add(key);
		});
		Set<String> allServiceCode = allKeys.stream().map(line->{
			String appendLine = line.replaceFirst(prefix.endsWith(".")?prefix:prefix+".","");
			if( appendLine.contains(".") ){
				return appendLine.substring(0,appendLine.indexOf("."));
			}
			return "";
		}).collect(Collectors.toSet());
		allServiceCode.stream().filter(key->StringUtil.isNotBlank(key)).forEach(serviceCode->{
		    allKeys.stream().filter(key->key.contains("."+serviceCode+".")).forEach(key->{
				T instance = releaseMap.computeIfAbsent(serviceCode, sn-> {
					return fillBeanWithDefault(new BeanWrapperImpl(clazz));
				});
				String affixKey = key.replace(prefix.concat(".").concat(serviceCode).concat("."),"");
				List<String> affixString = Splitter.on(".").splitToList(affixKey);
				if( affixString.size() == 1 ) {
					BeanPath.create(affixKey).set(instance, config.getProperty(key,""));
				} else {
					doGenerate(instance, affixString);
					BeanPath.create(affixKey).set(instance, config.getProperty(key,""));
				}
			});
		});
		return releaseMap;
	}

	public Map<String, T> getRemoteService(){
		return releaseMap;
	}

	private void doGenerate(T instance, List<String> affixString){
	    T currentInstance = instance;
		for( String key : affixString ) {
			BeanWrapperImpl bwi = new BeanWrapperImpl(currentInstance);
			if( key.contains("[") && key.contains("]") ){
				String fieldName = key.substring(0,key.indexOf("["));
				String middleValue = key.substring(key.indexOf("[")+1,key.indexOf("]"));
				try {
					Field field = currentInstance.getClass().getDeclaredField(fieldName);
					ParameterizedType pt = (ParameterizedType) field.getGenericType();
					Object value = bwi.getPropertyValue(fieldName);
					if ( pt.getRawType().getTypeName().equalsIgnoreCase("java.util.List") ){
						currentInstance = getCurrentInstanceList(bwi, fieldName, middleValue, pt, value);
					} else if( pt.getRawType().getTypeName().equalsIgnoreCase("java.util.Map") ){
						currentInstance = getCurrentInstanceMap(bwi, fieldName, middleValue, pt, value);
					}
				} catch (NoSuchFieldException e) {
				    log.error(e.getMessage());
				}
			}
		}
	}

	private T getCurrentInstanceList(BeanWrapperImpl bwi, String fieldName, String middleValue, ParameterizedType pt, Object value) {
		T currentInstance;
		Class rightType = (Class) pt.getActualTypeArguments()[0];
		List result = new ArrayList();
		if( value != null ){
			result = (List) value;
		}
		if( result.size() <= Integer.valueOf(middleValue) ){
			result.add(currentInstance = fillBeanWithDefault(new BeanWrapperImpl(rightType)));
		}else{
			currentInstance = (T) result.get(Integer.valueOf(middleValue));
		}
		bwi.setPropertyValue(fieldName, result);
		return currentInstance;
	}

	private T getCurrentInstanceMap(BeanWrapperImpl bwi, String fieldName, String middleValue, ParameterizedType pt, Object value) {
		T currentInstance;
		Class rightType = (Class) pt.getActualTypeArguments()[1];
		Map<String,Object> result = new HashMap<>();
		if( value !=null ){
			result = (Map<String, Object>) value;
		}
		if( !result.containsKey(middleValue) ){
			result.put(middleValue,currentInstance = fillBeanWithDefault(new BeanWrapperImpl(rightType)));
		}else{
			currentInstance = (T) result.get(middleValue);
		}
		bwi.setPropertyValue(fieldName, result);
		return currentInstance;
	}

	private T fillBeanWithDefault(BeanWrapperImpl instanceWrapper){
		for (PropertyDescriptor pd : instanceWrapper.getPropertyDescriptors()) {
			// 处理非 简单对象字段 默认为 null, 而 BeanPath 实例去进行 path 设值时 字段值不允许为空
			if( !ClassUtil.isSimpleValueType(pd.getPropertyType()) ){
			    if( pd.getPropertyType().isInterface() ){
			    	continue;
				}
				instanceWrapper.setPropertyValue(pd.getName(), BeanUtils.instantiateClass(pd.getPropertyType()));
			}
		}
		return (T) instanceWrapper.getRootInstance();
	}
}
